//Oui User Aceptance testing framework for web applications

var Oui = function(suites){
    this.UNDEFINED_VALUE;
	this.testPass = true;
    this.sutLoaded = false;
    this.runningTest = "";
	this.testSuites = [];
	this.isRunnerReady = false;
	this.isLoaderReady = false;
    this.testFrame = null;
    this.passedTests = 0;
    this.failedTests = 0;
    this.suitesLoaded = false;
    this.numberOfTests = 0;
    this.traceLog = $("ouiTestTrace");
    this.suitesDisplay = $("ouiTestSuites");
	if (suites != null && suites.length > 0 && (suites.constructor.toString().indexOf("Array") != -1)){
		this.testSuites = suites;
	}

}

Oui.prototype.isFrameReady = function(){
		return (this.isLoaderReady && this.isRunnerReady);
}

Oui.prototype.setTestResultCount = function(passed){
    if(passed){
        this.passedTests++;
    }else{
        this.failedTests++;
        if(this.testPass){
            this.testPass = false;
           this.setProgressStyle();
        }
    }
    $('passedTests').innerHTML = this.passedTests;
    $('failedTests').innerHTML = this.failedTests;
}

Oui.prototype.clearTestSuites = function(){
	this.testSuites = [];
}

Oui.prototype.addTestSuite = function( suite ){
	this.testSuites.push(suite);
}

Oui.prototype.setLoaderReady = function(){
	this.isLoaderReady = true;
}

Oui.prototype.setRunnerReady = function(){
	this.isRunnerReady = true;
}

Oui.prototype.showSuites = function(){
    for(var s=0; s < this.testSuites.length; s++){
        var sp = document.createElement("p");
        sp.innerHTML = this.testSuites[s];
        this.suitesDisplay.appendChild(sp);
        this.displaySuites(this.testSuites[s]);
    }
}

Oui.prototype.displaySuites = function(suiteName){
    var suite = eval("new suite"+suiteName+"(this);");
    var ul = document.createElement("ul");
    for(var test in suite){
        this.numberOfTests++;
        var li = document.createElement("li");
        li.setAttribute("id",suiteName+test);
        var r = document.createElement("input");
        r.setAttribute("type","checkbox");
        r.setAttribute("value",test);
        li.appendChild(r);
        li.innerHTML += test;
        ul.appendChild(li);
    }
    this.suitesDisplay.appendChild(ul);
}

Oui.prototype.run = function(){
	var counter=0;
    var waitTime = 1000;
	while(!this.isFrameReady() && counter < waitTime){
		counter++;
	}
    for(var s=0; s < this.testSuites.length; s++){
        this.runSuite(this.testSuites[s]);
    }
}

Oui.prototype.runSuite = function(suiteName){
    var suite = eval("new suite"+suiteName+"(this);");
    for(var test in suite){
        this.runningTest = suiteName + test;
        suite[test]();
    }
}

Oui.prototype.changeTestDisplayColor = function(pass){
    if(pass){
        $(this.runningTest).addClassName("passedTest");
    }else{
        $(this.runningTest).addClassName("failedTest"); 
    }
}

Oui.prototype.getProgressPerTest = function(){
    var totalBarWidth = $('ouiProgressBarContainer').getWidth();
    return (totalBarWidth/this.numberOfTests);
}

Oui.prototype.setProgressStyle = function(){
    if(!this.testPass){
      $('progressBar').addClassName("progressBarFail");
    }
}

Oui.prototype.progress = function ( pass ){
	this.setTestResultCount(pass);
    this.changeTestDisplayColor(pass);
	var width = this.getProgressPerTest();
	var pb = $('progressBar');

	if (pb.getWidth()){
		width += pb.getWidth();
	}
    var maxWidth = $('ouiProgressBarContainer').getWidth();
    if (Math.round(width) <= maxWidth){
        pb.setStyle({width: ''+ width + 'px'});
	}else if (pb.getWidth()< maxWidth){
        pb.setStyle({width: ''+ maxWidth + 'px'});
    }
}

Oui.prototype.traceWarning =  function(comment,failureMessage){
    var logRecord = this.getLogRecord(comment,failureMessage);
    logRecord.setAttribute("class", "passLog")
    var hr = document.createElement("hr");
    hr.setAttribute("class","dotted");
    logRecord.appendChild(hr);
    this.traceLog.appendChild(logRecord);
}

Oui.prototype.getLogRecord = function(comment,failureMessage){
    var element = document.createElement("p");
    if(comment){
        var commentElement = document.createElement("p");
        commentElement.innerHTML = "Comment: " + comment;
        element.appendChild(commentElement);
    }   
    if (failureMessage){
        var messageElement = document.createElement("p");
        messageElement.innerHTML = "Message: " + failureMessage;
        element.appendChild(messageElement);
    }
    return element;
}

Oui.prototype.traceResult = function(status, comment, failureMessage){
    this.progress(status);
    var msg = failureMessage;
    if(status){
        msg = "";
    }
    var logRecord = this.getLogRecord(comment,msg);
    if(!status){
        logRecord.setAttribute("class", "failLog");
    }
    logRecord.appendChild(document.createElement("hr"));
    this.traceLog.appendChild(logRecord);
}

/* Test helping functions */

Oui.prototype.setSutLoaded = function(){
    this.sutLoaded = true;
}

Oui.prototype.openPage = function(url){
    this.sut().location.href = url;
    //this.testFrame = window.open(url);
}

Oui.prototype.sut = function(){
   if(!this.testFrame){
       this.testFrame = parent.TestRunner;
   }
   return this.testFrame;
}

Oui.prototype.findById = function(elementId){

    if(this.sut().$){
        return this.sut().$(elementId);
    }else{
        return Element.extend(this.sut().document.getElementById(elementId));
    }
}

Oui.prototype.findAll = function(cssSelector){
    if(this.sut().$$){
        return this.sut().$$(cssSelector);
    }//else{
    //    return Selector.findChildElements(this.sut().document, $A(cssSelector));
    //}
}

Oui.prototype.clickConfirmOk = function(element){
    var originalConfirm = this.sut().confirm;
    var confirmCalled = false;
    this.sut().confirm = function(){
        confirmCalled = true;
        return true;
    };
    element.click();
    this.sut().confirm = originalConfirm;
    return confirmCalled;
}

Oui.prototype.clickConfirmCancel = function(element){
    var originalConfirm = this.sut().confirm;
    var confirmCalled = false;
    this.sut().confirm = function(){
        confirmCalled = true;
        return false;
    };
    element.click();
    this.sut().confirm = originalConfirm;
    return confirmCalled;
}

Oui.prototype.clickAlertOk = function(element){
    var originalAlert = this.sut().alert;
    var alertCalled = false;
    this.sut().alert = function(){
       alertCalled = true;
        return true;
    }
    element.click();
    this.sut().alert =  originalAlert;
    return alertCalled;
}

Oui.prototype.clickOpenWindow = function(element){
    var originalOpen = this.sut().open;
    var openedWindow = null;
    this.sut().open = function(sURL, sName, sFeatures, bReplace){
       openedWindow = originalOpen(sURL, sName, sFeatures, bReplace);
    }
    element.click();
    this.sut().open = originalOpen;
    return openedWindow;
}

/* JsUnit functions from now on */

Oui.prototype.argumentsIncludeComments = function(expectedNumberOfNonCommentArgs, args) {
    return args.length == expectedNumberOfNonCommentArgs + 1;
}

Oui.prototype.commentArg = function(expectedNumberOfNonCommentArgs, args) {
    if (this.argumentsIncludeComments(expectedNumberOfNonCommentArgs, args)){
        return args[0];
    }
    return null;
}

Oui.prototype.nonCommentArg = function(desiredNonCommentArgIndex, expectedNumberOfNonCommentArgs, args) {
    if (this.argumentsIncludeComments(expectedNumberOfNonCommentArgs, args)) {
        return args[desiredNonCommentArgIndex];
    } else {
        return args[desiredNonCommentArgIndex - 1];
    }
}

Oui.prototype._trueTypeOf = function(something) {
    var result = typeof something;
    try {
        switch (result) {
            case 'string':
            case 'boolean':
            case 'number':
                break;
            case 'object':
            case 'function':
                switch (something.constructor){
                    case String:
                        result = 'String';
                        break;
                    case Boolean:
                        result = 'Boolean';
                        break;
                    case Number:
                        result = 'Number';
                        break;
                    case Array:
                        result = 'Array';
                        break;
                    case RegExp:
                        result = 'RegExp';
                        break;
                    case Function:
                        result = 'Function';
                        break;
                    default:
                        var m = something.constructor.toString().match(/function\s*([^( ]+)\(/);
                        if (m){
                            result = m[1];
                        }else{
                            break;
                        }
                }
                break;
        }
    }
    finally {
        result = result.substr(0, 1).toUpperCase() + result.substr(1);
        return result;
    }
}

Oui.prototype._displayStringForValue = function(aVar) {
    var result = '<' + aVar + '>';
    if (!(aVar === null || aVar === this.UNDEFINED_VALUE)) {
        result += ' (' + this._trueTypeOf(aVar) + ')';
    }
    return result;
}

Oui.prototype._validateArguments = function(expectedNumberOfNonCommentArgs, args) {
    if (!( args.length == expectedNumberOfNonCommentArgs ||
           (args.length == expectedNumberOfNonCommentArgs + 1 && typeof(args[0]) == 'string') ))
        {
            this.traceWarning('', 'Incorrect arguments passed to assert function');
        }
}

Oui.prototype._assert = function(comment, booleanValue, failureMessage) {
    this.traceResult(booleanValue, comment, failureMessage);
}

Oui.prototype.assert = function() {
    this._validateArguments(1, arguments);
    var booleanValue = this.nonCommentArg(1, 1, arguments);

    if (typeof(booleanValue) != 'boolean'){
        this.traceWarning('','Bad argument to assert(boolean)');
    } 
    this._assert(this.commentArg(1, arguments), booleanValue === true, 'Call to assert(boolean) with false');
}

Oui.prototype.assertTrue = function() {
    this._validateArguments(1, arguments);
    var booleanValue = this.nonCommentArg(1, 1, arguments);

    if (typeof(booleanValue) != 'boolean'){
        this.traceWarning('','Bad argument to assertTrue(boolean)');
    }

    this._assert(this.commentArg(1, arguments), booleanValue === true, 'Call to assertTrue(boolean) with false');
}

Oui.prototype.assertFalse = function() {
    this._validateArguments(1, arguments);
    var booleanValue = this.nonCommentArg(1, 1, arguments);

    if (typeof(booleanValue) != 'boolean'){
        this.traceWarning('','Bad argument to assertFalse(boolean)');
    }

    this._assert(this.commentArg(1, arguments), booleanValue === false, 'Call to assertFalse(boolean) with true');
}

Oui.prototype.assertEquals = function() {
    this._validateArguments(2, arguments);
    var firstValue = this.nonCommentArg(1, 2, arguments);
    var secondValue = this.nonCommentArg(2, 2, arguments);
    this._assert(this.commentArg(2, arguments), firstValue === secondValue, 'Expected ' + this._displayStringForValue(firstValue) + ' but was ' + this._displayStringForValue(secondValue));
}

Oui.prototype.assertNotEquals = function() {
    this._validateArguments(2, arguments);
    var firstValue = this.nonCommentArg(1, 2, arguments);
    var secondValue = this.nonCommentArg(2, 2, arguments);
    this._assert(this.commentArg(2, arguments), firstValue !== secondValue, 'Expected not to be ' + this._displayStringForValue(secondValue));
}

Oui.prototype.assertNull = function() {
    this._validateArguments(1, arguments);
    var aVar = this.nonCommentArg(1, 1, arguments);
    this._assert(this.commentArg(1, arguments), aVar === null, 'Expected ' + this._displayStringForValue(null) + ' but was ' + this._displayStringForValue(aVar));
}

Oui.prototype.assertNotNull = function() {
    this._validateArguments(1, arguments);
    var aVar = this.nonCommentArg(1, 1, arguments);
    this._assert(this.commentArg(1, arguments), aVar !== null, 'Expected not to be ' + this._displayStringForValue(null));
}

Oui.prototype.assertUndefined = function() {
    this._validateArguments(1, arguments);
    var aVar = this.nonCommentArg(1, 1, arguments);
   this. _assert(this.commentArg(1, arguments), aVar === this.UNDEFINED_VALUE, 'Expected ' + this._displayStringForValue(this.UNDEFINED_VALUE) + ' but was ' + this._displayStringForValue(aVar));
}

Oui.prototype.assertNotUndefined = function() {
    this._validateArguments(1, arguments);
    var aVar = this.nonCommentArg(1, 1, arguments);
    this._assert(this.commentArg(1, arguments), aVar !== this.UNDEFINED_VALUE, 'Expected not to be ' + this._displayStringForValue(this.UNDEFINED_VALUE));
}

Oui.prototype.assertNaN = function() {
    this._validateArguments(1, arguments);
    var aVar = this.nonCommentArg(1, 1, arguments);
    this._assert(this.commentArg(1, arguments), isNaN(aVar), 'Expected NaN');
}

Oui.prototype.assertNotNaN = function() {
    this._validateArguments(1, arguments);
    var aVar = this.nonCommentArg(1, 1, arguments);
    this._assert(this.commentArg(1, arguments), !isNaN(aVar), 'Expected not NaN');
}

Oui.prototype.assertObjectEquals = function() {
    this._validateArguments(2, arguments);
    var firstValue = this.nonCommentArg(1, 2, arguments);
    var secondValue = this.nonCommentArg(2, 2, arguments);
    var firstValueType = this._trueTypeOf(firstValue);
    var msg = '';
    if (this.commentArg(2, arguments)) {
        msg = this.commentArg(2, arguments);
    }
    var isSame = (firstValue === secondValue);
    //shortpath for references to same object
    var isEqual = (firstValueType == this._trueTypeOf(secondValue) );
    if (isEqual && !isSame) {
        switch (firstValueType) {
            case 'String':
            case 'Number':
                isEqual = (firstValue == secondValue);
                break;
            case 'Boolean':
            case 'Date':
                isEqual = (firstValue === secondValue);
                break;
            case 'RegExp':
            case 'Function':
                isEqual = (firstValue.toString() === secondValue.toString());
                break;
            default: //Object | Array
                var i;
                isEqual = (firstValue.length === secondValue.length)
                if (isEqual){
                    for (i in firstValue){
                        this.assertObjectEquals(msg + ' found nested ' + firstValueType + '@' + i + '\n', firstValue[i], secondValue[i]);
                    }
                }
        }
        this._assert(msg, isEqual, 'Expected ' + this._displayStringForValue(firstValue) + ' but was ' + this._displayStringForValue(secondValue));
    }
}

Oui.prototype.assertArrayEquals = function(){
    this.assertObjectEquals(arguments);
}

Oui.prototype.assertEvaluatesToTrue = function() {
    this._validateArguments(1, arguments);
    var value = this.nonCommentArg(1, 1, arguments);
    this.traceResult(value,'',this.commentArg(1, arguments));
}

Oui.prototype.assertEvaluatesToFalse = function() {
    this._validateArguments(1, arguments);
    var value = this.nonCommentArg(1, 1, arguments);
    this.traceResult(value,'',this.commentArg(1, arguments));
}

Oui.prototype.assertHTMLEquals = function() {
    this._validateArguments(2, arguments);
    var firstValue = this.nonCommentArg(1, 2, arguments);
    var secondValue = this.nonCommentArg(2, 2, arguments);
    var firstStandardized = this.standardizeHTML(firstValue);
    var secondStandardized = this.standardizeHTML(secondValue);
    this._assert(this.commentArg(2, arguments), firstStandardized === secondStandardized, 'Expected ' + this._displayStringForValue(firstStandardized) + ' but was ' + this._displayStringForValue(secondStandardized));
}

Oui.prototype.assertHashEquals = function() {
    this._validateArguments(2, arguments);
    var firstValue = this.nonCommentArg(1, 2, arguments);
    var secondValue = this.nonCommentArg(2, 2, arguments);
    for (var firstKey in firstValue) {
        this.assertNotUndefined("Expected hash had key " + firstKey + " that was not found", secondValue[firstKey]);
        this.assertEquals(
                "Value for key " + firstKey + " mismatch - expected = " + firstValue[firstKey] + ", actual = " + secondValue[firstKey],
                firstValue[firstKey], secondValue[firstKey]
                );
    }
    for (var secondKey in secondValue) {
        this.assertNotUndefined("Actual hash had key " + secondKey + " that was not expected", firstValue[secondKey]);
    }
}

Oui.prototype.assertRoughlyEquals = function() {
    this._validateArguments(3, arguments);
    var expected = this.nonCommentArg(1, 3, arguments);
    var actual = this.nonCommentArg(2, 3, arguments);
    var tolerance = this.nonCommentArg(3, 3, arguments);
    this.assertTrue(
            "Expected " + expected + ", but got " + actual + " which was more than " + tolerance + " away",
            Math.abs(expected - actual) < tolerance
            );
}

Oui.prototype.assertContains = function() {
    this._validateArguments(2, arguments);
    var contained = this.nonCommentArg(1, 2, arguments);
    var container = this.nonCommentArg(2, 2, arguments);
    this.assertTrue(
            "Expected '" + container + "' to contain '" + contained + "'",
            container.indexOf(contained) != -1
            );
}

Oui.prototype.standardizeHTML = function(html) {
    var translator = document.createElement("DIV");
    translator.innerHTML = html;
    return translator.innerHTML;
}
